//  方法函数
// 1\如果数组所有元素满足函数条件，则返回true
// 调用时，如果省略第二个参数，则默认传递布尔值
// 例 all([4,12,2],x=>x>1)   true
const all =(arr, fn = Boolean) => arr.every(fn);

// 2 判断数组中的元素是否都相等
// 例 allEqual([1,2,12,2])  false
const allEqual = arr => arr.every(val => val === arr[0]);

// 3 检查两个数字是否近似相等，差异值可以通过传参的形式进行设置
// approximatelyEqual(Math.PI / 2.0 , 1.5708)  true
const approximatelyEqual = (v1,v2,epsilon = 0.001)=>Math.abs(v1 - v2) < epsilon


// 4 将没有逗号或双引号的元素转换成带有逗号分隔符的字符串即csv格式识别的形式
//  例 arrayToCSV([['a','b'],['c','d']]) '"a","b"\n"c","d"'
const arrayToCSV = (arr,delimiter = ',') =>
	arr.map(v => v.map(x=> '"${x}"').join(delimiter)).join('\n');


// 5 将数组元素转换为《li》标记
//  例 arrayTohtmlList(['item1','item2'],'mylist')
 const arrayTohtmlList = (arr,listID)=>(
	el=>(
	 (el = document.querySelect('#'+listID)),
	 (el.innerHTML+= arr.map(item=>'<li>${item}</li>').join(''))
	)
 )();


// 6 执行一个函数，将剩余的参数传回函数当参数。返回相应的结果，并能捕获异常
const attempt = (fn, ...args)=>{
	try{
		return fn(...args)
	} catch(e){
		return e instanceof Error ? e : new Error(e);
	}
}
var elements = attempt(function(selector){
	return document.querySelectorAll(selector)
},'>_>')
if(elements instanceof Error) elements = [];


// 7 返回两个或多个数的平均数
// 例 average(..[1,2,3]) 或 average(1,2,3)
const average = (...nums) => nums.reduce((acc,val)=>acc+val, 0) / nums.length

// 8 通过map（）函数将对象转换成数组再调用reduce()函数进行累加，然后根据数组长度返回平均值
// 例 averageBy([{n:1},{n:12}] , o => 0.n 或 ‘n’ )  
const averageBy = (arr,fn)=>
	arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc,val)=>
	acc+val, 0) / arr.length
	
// 9 此函数包含两个参数，类型都为数组，依据第二个参数的真假条件,
// 将一个参数的数组进行分组，条件为真的放入第一个数组，其他的放入第二个数组
// 例  bifurcate(['beep','boop','foo','bar'],[true,true,false,true])
//  [['beep','boop','bar'],['foo']]
const bifurcate =(arr,filter) =>
	arr.reduce((acc,val,i) => (acc[filter[i] ? 0 : 1].push(val),acc),[[],[]])
	


//  10 将数组按照指定的函数逻辑进行分组，满足函数条件的逻辑为真
// 放入第一个数组，其他的放入第二个数组
// 例  bifurcate(['beep','boop','foo','bar'],x=>x[0] === 'b')
//  [['beep','boop','bar'],['foo']]
const bifurcateBy = (arr,fn)=>
	arr.reduce((acc,val,i)=>(acc[fn(val,i) ? 0 : 1].push(val),acc),[[],[]])

// 11 用于检测页面是否滚动到页面底部
// bottomVisible() true
const bottomVisible = () =>
	document.documentElement.clientHeight + window.scrollY 
	>= (document.documentElement.scrollHeight || document.documentElement.clientHeight)

// 12 返回字符串的字节长度。用到了blob对象，blob对象代表了一段二进制数据
 // byteSize('')  4 ("Hello World") 11
 const byteSize = str => new Blob([str]).size;
 
 // 13 es6的展开语法在数组中的运用
 // 例 capitalize('fooBar') 'FooBar'  或 ('fooBar',true) 'FooBar'
 const capitalize = ([first,...rest]) => first.toUpperCase() + rest.join('')
 
 // 14 将一个句子中的每个单词首字母转换成大写字母
 // capitalizeEveryWord('hello world')  'Hello World'
 const capitalizeEveryWord = str => str.replace(/\b[a-z]/g , char=> char.toUpperCase())
 
 // 15 将非数值的值转换成数组对象
 //  castArray('foo')  ['foo'] 或 ([1]) [1]
 const castArray = val => (array.isArray(val) ? val : [val])
 
 // 16 将数组中移除值为false的内容
 //  compact([0,1,false,2,'',3,'a','e'*23,NaN,'s',34])  [1,2,3,'a','s',34]
 const compact = arr => arr.filter(Boolean)
 
 // 17 统计数组中某个值出现的次数
 // countOccurrences([1,1,2,1,2,3],1)  3
 const countOccurrences = (arr,val) => arr.reduce((a,v)=>(v=== val ? a+1 : a),0)
 
// 18 此代码段使用existSync() 检查目录是否存在
// 例 createDirIfNotExists('test') creates the directory 'test',if it doesnot exist
const fs = require('fs')
const createDirIfNotExists = dir => (!fs.existSync(dir) ? fs.mkdirSync(dir) : undefined)

//  19 返回当前访问的url地址
//  currentURL() 'http地址'
const currentURL = () =>window.location.href

// 20 返回当前是今年的第几天
// dayOfYear(new Date())  
const dayOfYear = date => Math.floor((date - new Date(date.getFullYear(),0,0)) / 1000/ 60 /60 /24)

// 21 将字符串的首字母转换成小写字母
//  decapitalize('FoorBar') 'foobar'
const decapitalize = ([first,...rest]) => first.toLowerCase() + rest.join('')

// 22 以递归的形式，将多维数组展开成一维数组
// deepFlatten(1,[2],[[3],4],5)   [1,2,3,4,5]
const deepFlatten = arr => [].concat(...arr.map(v=>(Array.isArray(v) ? deepFlatten(v):v)))


// 23 去重对象的属性。如果对象中含有重复的属性，以前面的为准
// defaults（{a:1},{b:2},{b:4},{a:3}）   {a:1,b:2}
const defaults = (obj, ...defs) =>object.assign({},obj,...defs.reverse(),obj)

// 24 延迟函数的调用，即异步调用函数
// defer(console.log,'a'),console.log('b')   先显示b 再显示a
const defer = (fn,...args) => setTimeout(fn,1,...args)

// 25 将标准的度数转换维弧度
// degreesToRads(90.0)  ~1.5708
const degreesToRads = deg =>(deg*Math.PI) / 180.0

// 26 查找两个给定数组的差异，查找出前者数组在后者数组中不存在的元素
// difference([1,2,3],[1,2,4])  [3]
const difference = (a,b) =>{
	const s = new Set(b);
	return a.filter(x => !s.has(x))
}

// 27 通过给定的函数来处理需要对比差异的数组,查找前者数组在后者数组中不存在的元素
//  differenceBy([2.1,1.2],[2.3,3.4],Math.floor)   [1.2]
   // ([{x:2},{x:1}],[{x:1}],v=>v.x) [{x:2}]
   
const differenceBy = (a,b,fn) =>{
	const s = new Set(b.map(fn))
	return a.filter(x=> !s.has(fn(x)))
}

// 28 按照给定函数逻辑筛选需要对比差异的数组,查找出前者数组在后者数组中不存在元素
// differenceWith([1,1.2,1.5,3,0],[1.9,3,0],(a,b)=>Math.round(a)===Math.round(b)) [1,1.2]

const differenceWith = (arr,val,comp) => arr.filter(a=>val.findIndex(b=>comp(a,b)) === -1)

// 29 将输入的数字拆分成单个数字组成的数组
//  digitize(431)  [4,3,1]
const digitize = n => [...'${n}'].map(i=>parseInt(i))


// 30 计算两点之间的距离
// distance(1,1,2,3)   2.23606797749979
const distance = (x0,y0,x1,y1) => Math.hypot(x1-x0,y1-y0)

// 31 将给定的数组从左边开始删除 n 个元素
// drop([1,2,3])  [2,3]  或 ([1,2,3],2) [3]   或 ([1,2,3],42)  []
const drop = (arr,n=1) => arr.slice(n)

//  32 将给定的数组从右边开始删除 n 个元素
// dropRight([1,2,3]) [1,2]  或 ([1,2,3],2)  [1]   或 ([1,2,3],42)  []
const dropRight = (arr, n=1) => arr.slice(0,-n)

// 33 将给定的数组按照给定的函数条件从右开始删除,直到当前元素满足函数条件维true时停止删除,并返回数组剩余元素
// dropRigthWhile([1,2,3,4],n=>n<3)    [1,2] 
const dropRightWhile = (arr,func)=>{
	while(arr.length > 0 && !func(arr[arr.lenght - 1])) 
		arr = arr.slice(0,-1)
	return arr
}

// 34 按照给的函数条件筛选数组,不满足函数条件的将从数组中移除
// dropWhile([1,2,3,4],n=> n>=3)     [3,4]
const dropWhile = (arr,func) =>{
	while (arr.length > 0 && !func(arr[0]))
		arr = arr.slice(1)
	return arr
}

// 35  接收两个dom元素对象参数,判断后者是否是前者的子元素
// elementContains(document.querySelector('head'),document.querySelector('title'))   true
// elementContains(document.querySelector('body'),document.querySelector('body'))  false
const elementContains = (parent,child) => parent !== child && parent.contains(child)

// 36 移除数组中重复的元素
// filterNonUnique([1,2,2,3,4,4,5])    [1,2,3,4,5]
const filterNonUnique = arr => [...new Set(arr)]

// 37  按照给定的函数条件,查找第一个满足条件对象的键值
// findKey({
// 	barney:{age:36,active:true},
// 	fred:{age:40,active:false},
// 	pebbles:{age:1,active:true}
// },
// 	o => o['active']
// )   ‘barney’  

const findKey = (obj,fn) => Object.keys(obj).find(key => fn(obj[key],key,obj))


// 38 按照给定的函数条件筛选数组,将最后一个满足条件的元素进行删除
// findLast([1,2,3,4],n=>n %2 === 1)   3
const findLast = (arr,fn) => arr.filter(fn).pop()

// 39 按照指定数组的深度，将嵌套数组进行展开
// flatten([1,[2],3,4])  [1,2,3,4]  或 ([1,[2,[3,[4,5],6],7],8],2)  [1,2,3,[4,5],6,7,8]
const flatten = (arr,depth = 1)=>
	arr.reduce((a,v)=>a.concat(depth>1 && Array.isArray(v) ? flatten(v,depth - 1): v),[])
	
// 40 按照给定的函数条件，从数组的右边往左依次进行执行
// forEachRight([1,2,3,4],val=>console.log(val))     '4','3','2','1'

const forEachRight = (arr,callback) => arr.slice(0).reverse().forEach(callback)

// 41 按照给定的函数条件,支持三个参数作为输入（值，键，对象本身），进行迭代对象
// forOwn({foo:'bar',a:1},v=>console.log(v))   'bar',1
const forOwn = (obj,fn) => Object.keys(obj).forEach(key=>fn(obj[key],key,obj))

// 42  输出函数的名称
//  functinoName(Math.max) // max (Logged in debug...)
const functionName = fn => (console.debug(fn.name),fn)

// 43  从date对象里获取当前时间
// getColonTimeFromDate(new Date()) '08:38:00'
const getColonTimeFromDate = date => date.toTimeString().slice(0,8)

// 44 返回两个日期之间相差多少天
// getDayDiffBetweenDates(new Date('2019-01-13'),new Date('2019-01-15'))  2
const getDayDiffBetweenDates = (dateInitial,detaFinal) => 
	(dateFinal - detaInitial) / (1000 * 3600 * 24)

// 45  返回dom元素节点对应的属性值
// getStyle(document.querySelector('p'),'font-size')   16px
const getStyle = (el,ruleName) => getComputedStyle(el)[ruleName]


// 46 返回数据的类型
// getType(new Set([1,2,3])) 'set'
const getType = v => v === undefined ? 'undefined' : 
v=== null ? 'null' : v.constructor.name.toLowerCase()

//47 返回dom元素是否包含指定的class样式
//  hasClass(document.querySelector('p.special'),'special')   true
const hasClass = (el,className) => el.classList.contains(className)

// 48 输出数组的第一个元素
// head([1,2,3])   1
const head = arr => arr[0]

// 49 隐藏指定的dom元素
// hide(document.querySelectorAll('img'))   hides all <img> elements on the page
const hide = (...el) => [...el].forEach(e=>(e.style.display='none'))

// 50 将http网址重定向https网址
// httpsRedirect()  http://baidu.com  => https://baidu.com
const httpsRedirect = ()=>{
	if(location.protocol !== 'https:') location.replace('https://'+location.href.split('//')[1])
}

// 51 返回数组中某个值对应的所有索引值，如果不包含该值，则返回一个空数组
// indexOfAll([1,2,3],4) []  或 ([1,2,3,1,2,3],1) [0,3]
const indexOfAll = (arr,val) => arr.reduce( (acc,el,i)=>(el===val ? [...acc,i]:acc) , [])

// 52 返回数组中除最后一个元素的所有元素
// initial([1,2,3]) [1,2]  
const initial = arr => arr.slice(0,-1)

// 53 在给定的dom节点后插入新的节点内容
// insertAfter(document.getElementById('myid'),'<p>123</p>')    <div id='myid'></div><p>123</p>
const insertAfter = (el,htmlString) => el.insertAdjacentHTML('afterend',htmlString)

// 54  在给定的dom节点前插入新的节点内容
// insertBefore(document.getElementById('myid'),'<p>123</p>')    <p>123</p><div id='myid'></div>
const insertBefore = (el,htmlString) => el.insertAdjacentHTML('beforebegin',htmlString)  

// 55 返回两个数组元素之间的交集
// intersection([1,2,3],[4,3,2])   [2,3]
const intersection = (a,b)=>{
	const s = new Set(b)
	return a.filter(x => s.has(x))
}

// 56 按照给定的函数处理需要对比的数组元素，然后根据处理后的数组找出交集，最后从第一个数组中将对应的元素输出
// intersectionBy([2.1,1.2],[2.3,3.4],Math.floor)   [2.1]
const intersectionBy = (a,b,fn)=>{
	const s = new Set(b.map(fn))
	return a.filter(x => s.has(fn(x)))
}

// 57 按照给定的函数对比两个数组的差异吗，然后找出交集，最后从第一个数组中将对应的元素输出
// intersectionWith([1,1.2,1.5,3,0],[1.9,3.0,3.9],(a,b)=>Math.round(a) === Math.round(b))   [1.5,3,0]
const intersectionWith = (a,b,comp)=>
	a.filter(x=>b.findIndex(y=>comp(x,y)) !== -1)


//58 判断数据是否为指定的数据类型。是则返回true
// is(Array,[1])  true
const is = (type,val) => ![,null].includes(val) && val.constructor === type

//59 接收两个日期类型的参数，判断前者的日期是否晚于后者的日期
// isAfterDate(new Date(2010,10,21),new Date(2010,10,20))   true
const isAfterDate = (dateA,dateB) => dateA > dateB


// 60 检测两个单词是否相似
//  isAnagram('iceman','cinema')  true
const isAnagram = (str1,str2) =>{
	const normalize = str=> str.toLowerCase().replace(/[^a-z0-9]/gi,'').split('').sort().join('')
	return normalize(str1) === normalize(str2)
}

// 61 检测对象是否为类数组对象，是否可迭代
//  isArrayLike('bac')  true  或 (null)  false
const isArrayLike = obj => obj != null && typeof obj[Symbol.iterator] === 'function'

// 62 接收两个日期类型的参数,判断前者的日期是否早于后者的日期
// isBeforeDate(new Date(2010,10,20),new Date(2010,10,21))   true
const isBeforeDate = (dateA,dateB) => dateA < dateB

// 63 检查参数是否为布尔类型
// isBoolean(null) false
const isBoolean = val => typeof val === 'boolean'